/* msg.c : messaging system (assignment 2)
 */

#include <xeroskernel.h>

extern int send(unsigned int dest_pid, void *buffer, int buffer_len, pcb *sender){
	//dest_pid is the pid of the process the message is supposed to go to
	//buffer is the pointer to the message to be sent (source buffer)
	//buffer_len is the length of the source buffer in bytes
	//returns the number of bytes of the message if send is successful
	//returns -1 if PID is invalid
	//returns -2 process is trying to send to itself
	//returns -3 for other errors

/*
	Main algorithm:
	1. if parameters are invalid, put sender back to ready queue and return appropriate error code
	2. get the pcb of the intended receiver through the pid
	3. if pcb doesn't exist, put sender back to ready queue and return error code
	4. if intended receiver is not listening for messages, place sender in the receiver's sender queue and block the sender (save message, change state to STATE_SEND, save intended receiver's pid)
	5. if receiver is listening for messages or only listening from sender, copy message to receiver's buffer and place receiver and sender in the ready queue. 
	6. return number of bytes sent
*/
	//the pcb of the receiver process
	pcb *receiver;

	if(buffer == NULL || buffer_len < 0){
		ready(sender);
		return -3;
	}

	if(sender->pid == dest_pid){
		ready(sender);
		return -2;
	}

	//look for receiver process
	int i;
	for(i=0; i<MAX_PROC; i++){
		if(proctab[i].state==STATE_STOPPED)
			continue;
		receiver = &proctab[i];
		if(receiver->pid == dest_pid)
			break;
	} 
	//receiver not found
	if(receiver->pid != dest_pid){
		ready(sender);
		return -1;
	}

	//if receiver is listening for message from sender
	if(receiver->state==STATE_RECV && (*receiver->from_pid==sender->pid || *receiver->from_pid==0)){
		//copy message
		int ret = buffer_len > receiver->buffer_len ? receiver->buffer_len : buffer_len;
		blkcopy(receiver->buffer,buffer,ret);
		ready(sender);
		ready(receiver);
		receiver->ret = ret;
		return ret;
	}else{
		sender->state = STATE_SEND;
		sender->dest_pid = dest_pid;
		sender->buffer = buffer;
		sender->buffer_len = buffer_len;
		//add sender to receiver's sender queue
		if(receiver->senderQ == NULL){
			sender->next = NULL;
			receiver->senderQ = sender;
		}else{
			pcb *temp = receiver->senderQ;
			while(temp->next != NULL)
				temp = temp->next;
			temp->next = sender;
			sender->next = NULL;
		}
		return -3;
	}

}

extern int recv(unsigned int *from_pid, void *buffer, int buffer_len, pcb *receiver){
	//from_pid is the pid of the process the message is from
	//buffer is the pointer to the destination buffer
	//buffer_len is the length of the destination buffer
	//returns the number of bytes that were received
	//returns -1 if PID is invalid
	//returns -2 if process is trying to receive from iteself
	//returns -3 for other errors
	//pcb of sender process
	
/*
	Main algorithm:
	1. if parameters are invalid, put receiver back to ready queue and return appropriate error code
	2. if from_pid is 0 then get sender from the head of the receiver's sender queue. if sender queue does not exist, block the receiver (change state to STATE_RECV, save from_pid, and buffer information)
	3. if from_pid is not 0 then look for sender in the pcb table, returns error code if not found
	4. once found, if intended sender is sending to receiver copy message from sender's buffer to receiver's buffer, place both process back in the ready queue, and return the number of bytes received
	5. if sender is not sending, block receiver (see step 2 for details)
*/

	pcb *sender;
	
	//invalid parameters
	if(from_pid == NULL || buffer==NULL || buffer_len < 0){
		ready(receiver);
		return -3;
	}

	//if receiver is trying to receive from itself
	if(receiver->pid == *from_pid){
		ready(receiver);
		return -2;
	}

	//look for sender
	if(*from_pid ==0){
		//assign first pcb of sender queue to 'sender' and reassign head of the queue
		//return -1 if sender queue is empty
		if(receiver->senderQ == NULL){
			//block receiver
			receiver->state = STATE_RECV;
			receiver->from_pid = from_pid;
			receiver->buffer = buffer;
			receiver->buffer_len = buffer_len; 
			return -3;
		}		
		sender = receiver->senderQ;
		receiver->senderQ = receiver->senderQ->next;
	}else{
		//look for sender in pcb table
		int i;
		for(i=0; i<MAX_PROC; i++){
			if(proctab[i].state==STATE_STOPPED)
				continue;
			sender = &proctab[i];
			if(sender->pid == *from_pid)
				break;
		}
		//sender not found
		if(sender->pid != *from_pid){
			ready(receiver);
			return -1;
		}
	}

	//if sender is sending to receiver
	if(sender->state==STATE_SEND && sender->dest_pid==receiver->pid){
		//remove sender from sender queue, if it exists
		pcb *temp = receiver->senderQ;
		while(temp != NULL){
			if(temp->next == sender){
				temp->next = sender->next;
				break;
			}	
			temp = temp->next;
		}
		//copy message to receive buffer
		int ret = buffer_len > sender->buffer_len ? sender->buffer_len : buffer_len;
		blkcopy(buffer,sender->buffer,ret);
		ready(sender);
		ready(receiver);
		sender->ret = ret;
		return ret;
	}else{
		//block receiver
		receiver->state = STATE_RECV;
		receiver->from_pid = from_pid;
		receiver->buffer = buffer;
		receiver->buffer_len = buffer_len; 
		return -3;
	}
}
